using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using OsdmSoftware.CommonLibrary;

namespace OsdmSoftware.PocketPicture
{
    public class HueSaturationControl: Control
    {
        Color selectedColor = Color.White;
        int selectedHue;

        int selectedSaturation;

        byte selectedValue;

        //byte bitmapRenderedForValue = 230;
        int maxWidth, maxHeight, divSector, divHue;
        byte divSaturation;
        const byte maxValue = 230; // must be 254 or less, never 255 (otherwise byte overflow happens when cycling through byte variable)!
        bool initialized = false;
        bool mouseDown = false;
        Bitmap bmp;
        Orientation orientation;

        public Orientation Orientation
        {
            get
            {
                return orientation;
            }
            set
            {
                if (orientation == value)
                    return;
                orientation = value;
                initialized = false;
                //Initialize();
                if (bmp != null)
                {
                    bmp.Dispose();
                    bmp = null;
                }
                Invalidate();
            }
        }

        public int SelectedHue { get { return selectedHue; } }

        public int SelectedSaturation { get { return selectedSaturation; } }

        public byte SelectedValue
        {
            get
            {
                return selectedValue;
            }
            set
            {
                selectedValue = value;
                ComputeSelectedColor();
                Refresh();
            }
        }

        public void Initialize()
        {
            if (initialized)
                return;
            if (orientation == Orientation.Horizontal)
            {
#if PocketPC
                Graphics gr = this.CreateGraphics();
                int dpi = (int) gr.DpiX;
                gr.Dispose();
                maxWidth = dpi * 180 / 96;
                maxHeight = dpi * (maxValue + 1) / 192;
                divSector = dpi / 96 * 30;
                divSaturation = (byte) ((maxValue + 1) / maxHeight);
                divHue = 360 / maxWidth;
#else
                maxWidth = 360;
                divSector = 60;
                maxHeight = (maxValue + 1);
                divSaturation = 1;
                divHue = 1;
#endif
            }
            else
            {
#if PocketPC
                Graphics gr = this.CreateGraphics();
                int dpi = (int) gr.DpiY;
                gr.Dispose();
                maxHeight = dpi * 180 / 96;
                maxWidth = dpi * (maxValue + 1) / 192;
                divSector = dpi / 96 * 30;
                divSaturation = (byte) ((maxValue + 1) / maxWidth);
                divHue = 360 / maxHeight;
#else
                maxHeight = 360;
                divSector = 60;
                maxWidth = (maxValue + 1);
                divSaturation = 1;
                divHue = 1;
#endif
            }

            Width = maxWidth;
            Height = maxHeight;
            initialized = true;
        }

        protected override void Dispose(bool disposing)
        {
            if (bmp != null)
            {
                bmp.Dispose();
                bmp = null;
            }
            base.Dispose(disposing);
        }

        protected unsafe void RenderBitmap()
        {
            Initialize();
            if (bmp != null)
                return;

            if (bmp != null)
            {
                bmp.Dispose();
                bmp = null;
            }

            byte value = maxValue;
            //bitmapRenderedForValue = value;

            bmp = new Bitmap(maxWidth, maxHeight, PixelFormat.Format32bppRgb);
            BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, maxWidth, maxHeight), ImageLockMode.ReadWrite, ColorBgra.PixelFormat);
            byte* pTargetRow = (byte*) bmpData.Scan0.ToPointer();

            if (orientation == Orientation.Horizontal)
            {
                for (byte p = 0; p <= maxValue; p += divSaturation, pTargetRow += bmpData.Stride)
                {
                    //byte s = (byte) ((maxValue - row * divy) * value / maxValue);
                    //byte s = (byte) (maxValue - row * divy);
                    //byte p = (byte) (value - s);
                    byte s = (byte) (value - p);
                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;

                    if (s == 0) // Is it really needed?
                    {
                        for (int col = 0; col < maxWidth; col++, pTargetPixel++)
                        {
                            *pTargetPixel = ColorBgra.FromRgb(value, value, value);
                        }
                    }
                    else
                    {

                        // The color wheel consists of 6 sectors.
                        // Lets work it sector by sector.
                        for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetPixel++)
                        {
                            *pTargetPixel = ColorBgra.FromRgb(value, (byte) (value - (s * (divSector - fractionalSector) / divSector)), p);
                        }

                        for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetPixel++)
                        {
                            *pTargetPixel = ColorBgra.FromRgb((byte) (value - (s * fractionalSector / divSector)), value, p);
                        }

                        for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetPixel++)
                        {
                            *pTargetPixel = ColorBgra.FromRgb(p, value, (byte) (value - (s * (divSector - fractionalSector) / divSector)));
                        }

                        for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetPixel++)
                        {
                            *pTargetPixel = ColorBgra.FromRgb(p, (byte) (value - (s * fractionalSector / divSector)), value);
                        }

                        for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetPixel++)
                        {
                            *pTargetPixel = ColorBgra.FromRgb((byte) (value - (s * (divSector - fractionalSector) / divSector)), p, value);
                        }

                        for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetPixel++)
                        {
                            *pTargetPixel = ColorBgra.FromRgb(value, p, (byte) (value - (s * fractionalSector / divSector)));
                        }
                    }
                }
            }
            else
            {
                // The color wheel consists of 6 sectors.
                // Lets work it sector by sector.
                for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetRow += bmpData.Stride)
                {
                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                    for (byte p = 0; p <= maxValue; p += divSaturation, pTargetPixel++)
                    {
                        byte s = (byte) (value - p);
                        *pTargetPixel = ColorBgra.FromRgb(value, (byte) (value - (s * (divSector - fractionalSector) / divSector)), p);
                    }
                }

                for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetRow += bmpData.Stride)
                {
                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                    for (byte p = 0; p <= maxValue; p += divSaturation, pTargetPixel++)
                    {
                        byte s = (byte) (value - p);
                        *pTargetPixel = ColorBgra.FromRgb((byte) (value - (s * fractionalSector / divSector)), value, p);
                    }
                }

                for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetRow += bmpData.Stride)
                {
                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                    for (byte p = 0; p <= maxValue; p += divSaturation, pTargetPixel++)
                    {
                        byte s = (byte) (value - p);
                        *pTargetPixel = ColorBgra.FromRgb(p, value, (byte) (value - (s * (divSector - fractionalSector) / divSector)));
                    }
                }

                for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetRow += bmpData.Stride)
                {
                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                    for (byte p = 0; p <= maxValue; p += divSaturation, pTargetPixel++)
                    {
                        byte s = (byte) (value - p);
                       *pTargetPixel = ColorBgra.FromRgb(p, (byte) (value - (s * fractionalSector / divSector)), value);
                    }
                }

                for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetRow += bmpData.Stride)
                {
                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                    for (byte p = 0; p <= maxValue; p += divSaturation, pTargetPixel++)
                    {
                        byte s = (byte) (value - p);
                        *pTargetPixel = ColorBgra.FromRgb((byte) (value - (s * (divSector - fractionalSector) / divSector)), p, value);
                    }
                }

                for (int fractionalSector = 0; fractionalSector < divSector; fractionalSector++, pTargetRow += bmpData.Stride)
                {
                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                    for (byte p = 0; p <= maxValue; p += divSaturation, pTargetPixel++)
                    {
                        byte s = (byte) (value - p);
                        *pTargetPixel = ColorBgra.FromRgb(value, p, (byte) (value - (s * fractionalSector / divSector)));
                    }
                }
            }

            bmp.UnlockBits(bmpData);
        }

        public Color SelectedColor
        {
            get
            {
                return selectedColor;
            }
            set
            {
                if (selectedColor == value)
                    return;

                selectedColor = value;
                // In this function, R, G, and B values must be scaled 
                // to be between 0 and 1.
                // HsvColor.Hue will be a value between 0 and 360, and 
                // HsvColor.Saturation and value are between 0 and 1.

                double min;
                double max;
                double delta;

                double r = (double) selectedColor.R / 255;
                double g = (double) selectedColor.G / 255;
                double b = (double) selectedColor.B / 255;

                double h;
                double s;
                double v;

                min = Math.Min(Math.Min(r, g), b);
                max = Math.Max(Math.Max(r, g), b);
                v = max;
                delta = max - min;

                if (max == 0 || delta == 0)
                {
                    // R, G, and B must be 0, or all the same.
                    // In this case, S is 0, and H is undefined.
                    // Using H = 0 is as good as any...
                    s = 0;
                    h = 0;
                }
                else
                {
                    s = delta / max;
                    if (r == max)
                    {
                        // Between Yellow and Magenta
                        h = (g - b) / delta;
                    }
                    else if (g == max)
                    {
                        // Between Cyan and Yellow
                        h = 2 + (b - r) / delta;
                    }
                    else
                    {
                        // Between Magenta and Cyan
                        h = 4 + (r - g) / delta;
                    }

                }
                // Scale h to be between 0 and 360. 
                // This may require adding 360, if the value
                // is negative.
                h *= 60;

                if (h < 0)
                {
                    h += 360;
                }

                selectedHue = (int) h;
                selectedSaturation = (int) (s * maxValue);
                selectedValue = (byte) (v * 255);
                Refresh();
            }
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            PaintOnRectangle(pe.Graphics, pe.ClipRectangle);
            base.OnPaint(pe);
        }

        protected void PaintOnRectangle(Graphics gr, Rectangle rect)
        {
            RenderBitmap();

            gr.DrawImage(bmp, rect, rect, GraphicsUnit.Pixel);
            if (orientation == Orientation.Horizontal)
            {
                gr.DrawEllipse(new Pen(Color.Black, 3), new Rectangle((selectedHue / divHue) - 3, (bmp.Height - 1 - selectedSaturation / divSaturation) - 3, 6, 6));
            }
            else
            {
                gr.DrawEllipse(new Pen(Color.Black, 3), new Rectangle((bmp.Width - 1 - selectedSaturation / divSaturation) - 3, (selectedHue / divHue) - 3, 6, 6));
            }
        }

        protected void RepaintRectangle(Rectangle rect)
        {
            Graphics gr = CreateGraphics();
            PaintOnRectangle(gr, rect);
            gr.Dispose();
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //base.OnPaintBackground(e);
        }

        public event EventHandler SelectedColorChanged;

        protected void OnSelectedColorChanged()
        {
            if (SelectedColorChanged != null)
                SelectedColorChanged(this, EventArgs.Empty);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            mouseDown = true;
            this.Capture = true;
            OnMouseMove(e);
            //mouseDown = false;
            base.OnMouseDown(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (mouseDown)
            {
                Rectangle oldRect;
                if (orientation == Orientation.Horizontal)
                {
                    oldRect = new Rectangle((selectedHue / divHue) - 6, (bmp.Height - 1 - selectedSaturation / divSaturation) - 6, 12, 12);

                    selectedHue = e.X * divHue;
                    selectedSaturation = (maxHeight - 1 - e.Y) * divSaturation;
                }
                else
                {
                    oldRect = new Rectangle((bmp.Width - 1 - selectedSaturation / divSaturation) - 6, (selectedHue / divHue) - 6, 12, 12);

                    selectedHue = e.Y * divHue;
                    selectedSaturation = (maxWidth - 1 - e.X) * divSaturation;
                }

                if (selectedHue > 359)
                    selectedHue = 359;
                else if (selectedHue < 0)
                    selectedHue = 0;

                if (selectedSaturation > maxValue)
                    selectedSaturation = maxValue;
                else if (selectedSaturation < 0)
                    selectedSaturation = 0;

                ComputeSelectedColor();
                RepaintRectangle(oldRect);
                //Invalidate(new Rectangle((selectedHue / divx) - 4, (bmp.Height - 1 - selectedSaturation / divy) - 4, 8, 8));

                OnSelectedColorChanged();
            }
            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (mouseDown)
                OnMouseMove(e);
            mouseDown = false;
            this.Capture = false;
            base.OnMouseUp(e);
        }

        static public Color HSVToColor(int selectedHue, int selectedSaturation, int selectedValue)
        {
            double h;
            double s;
            double v;

            double r = 0;
            double g = 0;
            double b = 0;

            // Scale Hue to be between 0 and 360. Saturation
            // and value scale to be between 0 and 1.
            h = (double) selectedHue % 360;
            s = (double) selectedSaturation / maxValue;
            v = (double) selectedValue / 255;

            if (s == 0)
            {
                // If s is 0, all colors are the same.
                // This is some flavor of gray.
                r = v;
                g = v;
                b = v;
            }
            else
            {
                double p;
                double q;
                double t;

                double fractionalSector;
                int sectorNumber;
                double sectorPos;

                // The color wheel consists of 6 sectors.
                // Figure out which sector you're in.
                sectorPos = h / 60;
                sectorNumber = (int) (Math.Floor(sectorPos));

                // get the fractional part of the sector.
                // That is, how many degrees into the sector
                // are you?
                fractionalSector = sectorPos - sectorNumber;

                // Calculate values for the three axes
                // of the color. 
                p = v * (1 - s);
                q = v * (1 - (s * fractionalSector));
                t = v * (1 - (s * (1 - fractionalSector)));

                // Assign the fractional colors to r, g, and b
                // based on the sector the angle is in.
                switch (sectorNumber)
                {
                    case 0:
                        r = v;
                        g = t;
                        b = p;
                        break;

                    case 1:
                        r = q;
                        g = v;
                        b = p;
                        break;

                    case 2:
                        r = p;
                        g = v;
                        b = t;
                        break;

                    case 3:
                        r = p;
                        g = q;
                        b = v;
                        break;

                    case 4:
                        r = t;
                        g = p;
                        b = v;
                        break;

                    case 5:
                        r = v;
                        g = p;
                        b = q;
                        break;
                }
            }

            return Color.FromArgb((int) (r * 255), (int) (g * 255), (int) (b * 255));
        }

        protected void ComputeSelectedColor()
        {
            selectedColor = HSVToColor(selectedHue, selectedSaturation, selectedValue);
        }
    }

    public class LuminanceControl: Control
    {
        int selectedValue = 128;
        Color selectedColor = Color.White;
        Color bitmapRenderedForColor = Color.White;
        bool mouseDown = false;
        Bitmap bmp;
        Orientation orientation = Orientation.Horizontal;

        public Orientation Orientation
        {
            get
            {
                return orientation;
            }
            set
            {
                if (orientation == value)
                    return;
                orientation = value;
                if (bmp != null)
                {
                    bmp.Dispose();
                    bmp = null;
                }
                Invalidate();
            }
        }

        public Color SelectedColor
        {
            get
            {
                return selectedColor;
            }
            set
            {
                if (selectedColor == value)
                    return;
                selectedColor = value;
                Refresh();
            }
        }

        public int SelectedValue
        {
            get
            {
                return selectedValue;
            }
            set
            {
                if (selectedValue == value)
                    return;
                selectedValue = value;
                Refresh();
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (bmp != null)
            {
                bmp.Dispose();
                bmp = null;
            }

            base.Dispose(disposing);
        }

        public unsafe void RenderBitmap()
        {
            if (bitmapRenderedForColor == selectedColor && bmp != null)
                return;

            if (bmp != null)
            {
                bmp.Dispose();
                bmp = null;
            }

            bitmapRenderedForColor = selectedColor;

            if (orientation == Orientation.Vertical)
            {
                bmp = new Bitmap(Width / 2, Height - 2, PixelFormat.Format32bppRgb);
                BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, ColorBgra.PixelFormat);
                byte* pTargetRow = (byte*) bmpData.Scan0.ToPointer();
                int maxRow = bmp.Height - 1;
                for (int row = maxRow; row >= 0; row--, pTargetRow += bmpData.Stride)
                {
                    ColorBgra setColorTo = ColorBgra.FromRgb((byte) (bitmapRenderedForColor.R * row / maxRow),
                        (byte) (bitmapRenderedForColor.G * row / maxRow),
                        (byte) (bitmapRenderedForColor.B * row / maxRow));

                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                    for (int col = 0; col < bmp.Width; col++, pTargetPixel++)
                    {
                        *pTargetPixel = setColorTo;
                    }
                }

                bmp.UnlockBits(bmpData);
            }
            else
            {
                bmp = new Bitmap(Width - 2, Height / 2, PixelFormat.Format32bppRgb);
                BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, ColorBgra.PixelFormat);
                byte* pTargetRow = (byte*) bmpData.Scan0.ToPointer();
                int maxRow = bmp.Width - 1;
                for (int col = 0; col < bmp.Height; col++, pTargetRow += bmpData.Stride)
                {
                    ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                    for (int row = maxRow; row >= 0; row--, pTargetPixel++)
                    {
                        ColorBgra setColorTo = ColorBgra.FromRgb((byte) (bitmapRenderedForColor.R * row / maxRow),
                            (byte) (bitmapRenderedForColor.G * row / maxRow),
                            (byte) (bitmapRenderedForColor.B * row / maxRow));

                        *pTargetPixel = setColorTo;
                    }
                }

                bmp.UnlockBits(bmpData);
            }
        }

        protected void PaintOnRectangle(Graphics graphics, Rectangle rect)
        {
            RenderBitmap();
            if (rect.Width == 0 || rect.Height == 0)
                return;

            Bitmap tmpBitmap;
            try
            {
                tmpBitmap = new Bitmap(rect.Width, rect.Height/*, PixelFormat.Format32bppRgb*/);
            }
            catch(Exception e)
            {
                graphics.DrawString("Error " + e.Message, new Font("Arial", 8, FontStyle.Regular), new SolidBrush(Color.Black), 0, 0);
                return;
            }

            Graphics gr = Graphics.FromImage(tmpBitmap);
            gr.FillRectangle(new SolidBrush(BackColor), new Rectangle(0, 0, rect.Width, rect.Height));
            gr.DrawRectangle(new Pen(Color.Gray), new Rectangle(-rect.Left, -rect.Top, bmp.Width, bmp.Height));
            Rectangle r = Rectangle.Intersect(new Rectangle(1 - rect.Left, 1 - rect.Top, bmp.Width, bmp.Height), new Rectangle(0, 0, rect.Width, rect.Height));
            gr.DrawImage(bmp, r, r, GraphicsUnit.Pixel);

            if (orientation == Orientation.Vertical)
            {
                int selectedPos = (255 - selectedValue) * bmp.Height / 255 + 1 - rect.Top;
                gr.FillPolygon(new SolidBrush(Color.FromArgb(bitmapRenderedForColor.R * selectedValue / 255,
                    bitmapRenderedForColor.G * selectedValue / 255,
                    bitmapRenderedForColor.B * selectedValue / 255
                    )), new Point[] {
                    new Point(4 + bmp.Width - rect.Left, selectedPos),
                    new Point(Width - 3 - rect.Left, selectedPos - (Width - 3 - 4 - bmp.Width)),
                    new Point(Width - 3 - rect.Left, selectedPos + (Width - 3 - 4 - bmp.Width))
                });
                gr.DrawPolygon(new Pen(Color.Gray), new Point[] {
                    new Point(4 + bmp.Width - rect.Left, selectedPos),
                    new Point(Width - 3 - rect.Left, selectedPos - (Width - 3 - 4 - bmp.Width)),
                    new Point(Width - 3 - rect.Left, selectedPos + (Width - 3 - 4 - bmp.Width))
                });
            }
            else
            {
                int selectedPos = (255 - selectedValue) * bmp.Width / 255 + 1 - rect.Left;
                gr.FillPolygon(new SolidBrush(Color.FromArgb(bitmapRenderedForColor.R * selectedValue / 255,
                    bitmapRenderedForColor.G * selectedValue / 255,
                    bitmapRenderedForColor.B * selectedValue / 255
                    )), new Point[] {
                    new Point(selectedPos, 4 + bmp.Height - rect.Top),
                    new Point(selectedPos - (Height - 3 - 4 - bmp.Height), Height - 3 - rect.Top),
                    new Point(selectedPos + (Height - 3 - 4 - bmp.Height), Height - 3 - rect.Top)
                });
                gr.DrawPolygon(new Pen(Color.Gray), new Point[] {
                    new Point(selectedPos, 4 + bmp.Height - rect.Top),
                    new Point(selectedPos - (Height - 3 - 4 - bmp.Height), Height - 3 - rect.Top),
                    new Point(selectedPos + (Height - 3 - 4 - bmp.Height), Height - 3 - rect.Top)
                });
            }

            gr.Dispose();

            graphics.DrawImage(tmpBitmap, new Rectangle(0, 0, tmpBitmap.Width, tmpBitmap.Height), rect, GraphicsUnit.Pixel);
            tmpBitmap.Dispose();
        }

        protected void RepaintRectangle(Rectangle rect)
        {
            Graphics gr = CreateGraphics();
            PaintOnRectangle(gr, rect);
            gr.Dispose();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            PaintOnRectangle(e.Graphics, e.ClipRectangle);
            base.OnPaint(e);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //base.OnPaintBackground(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            mouseDown = true;
            this.Capture = true;
            OnMouseMove(e);
            //mouseDown = false;
            base.OnMouseDown(e);
        }

        public event EventHandler SelectedValueChanged;

        protected void OnSelectedValueChanged()
        {
            if (SelectedValueChanged != null)
                SelectedValueChanged(this, EventArgs.Empty);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (mouseDown)
            {
                //int selectedPos = selectedValue * bmp.Height / 255 + 1;

                //int newValue = (e.Y - 1) * 255 / bmp.Height;
                if (orientation == Orientation.Vertical)
                {
                    selectedValue = 255 - (e.Y - 1) * 255 / bmp.Height;
                }
                else
                {
                    selectedValue = 255 - (e.X - 1) * 255 / bmp.Width;
                }

                if (selectedValue < 0)
                    selectedValue = 0;
                else if (selectedValue > 255)
                    selectedValue = 255;

                Refresh();

                OnSelectedValueChanged();
            }
            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (mouseDown)
                OnMouseMove(e);
            mouseDown = false;
            this.Capture = false;
            base.OnMouseUp(e);
        }
    }
}
